/*
===============================================================================

  FILE:  laswriter_las.cpp
  
  CONTENTS:
  
    see corresponding header file
  
  PROGRAMMERS:
  
    martin.isenburg@gmail.com
  
  COPYRIGHT:
  
    (c) 2007-2011, Martin Isenburg, LASSO - tools to catch reality

    This is free software; you can redistribute and/or modify it under the
    terms of the GNU Lesser General Licence as published by the Free Software
    Foundation. See the COPYING file for more information.

    This software is distributed WITHOUT ANY WARRANTY and without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  
  CHANGE HISTORY:
  
    see corresponding header file
  
===============================================================================
*/
#include "laswriter_las.hpp"

#include "bytestreamout_nil.hpp"
#include "bytestreamout_file.hpp"
#include "bytestreamout_ostream.hpp"
#include "laswritepoint.hpp"

#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif

#include <stdlib.h>
#include <string.h>

BOOL LASwriterLAS::refile(FILE* file)
{
  if (stream == 0) return FALSE;
  if (this->file) this->file = file;
  return ((ByteStreamOutFile*)stream)->refile(file);
}

BOOL LASwriterLAS::open(const LASheader* header, U32 compressor, I32 requested_version, I32 chunk_size)
{
  ByteStreamOut* out = new ByteStreamOutNil();
  return open(out, header, compressor, requested_version, chunk_size);
}

BOOL LASwriterLAS::open(const char* file_name, const LASheader* header, U32 compressor, I32 requested_version, I32 chunk_size, U32 io_buffer_size)
{
  if (file_name == 0)
  {
    fprintf(stderr,"ERROR: file name pointer is zero\n");
    return FALSE;
  }

  file = fopen(file_name, "wb");
  if (file == 0)
  {
    fprintf(stderr, "ERROR: cannot open file '%s'\n", file_name);
    return FALSE;
  }

  if (setvbuf(file, NULL, _IOFBF, io_buffer_size) != 0)
  {
    fprintf(stderr, "WARNING: setvbuf() failed with buffer size %u\n", io_buffer_size);
  }

  ByteStreamOut* out;
  if (IS_LITTLE_ENDIAN())
    out = new ByteStreamOutFileLE(file);
  else
    out = new ByteStreamOutFileBE(file);

  return open(out, header, compressor, requested_version, chunk_size);
}

BOOL LASwriterLAS::open(FILE* file, const LASheader* header, U32 compressor, I32 requested_version, I32 chunk_size)
{
  if (file == 0)
  {
    fprintf(stderr,"ERROR: file pointer is zero\n");
    return FALSE;
  }

#ifdef _WIN32
  if (file == stdout)
  {
    if(_setmode( _fileno( stdout ), _O_BINARY ) == -1 )
    {
      fprintf(stderr, "ERROR: cannot set stdout to binary (untranslated) mode\n");
    }
  }
#endif

  ByteStreamOut* out;
  if (IS_LITTLE_ENDIAN())
    out = new ByteStreamOutFileLE(file);
  else
    out = new ByteStreamOutFileBE(file);

  return open(out, header, compressor, requested_version, chunk_size);
}

BOOL LASwriterLAS::open(ostream& stream, const LASheader* header, U32 compressor, I32 requested_version, I32 chunk_size)
{
  ByteStreamOut* out;
  if (IS_LITTLE_ENDIAN())
    out = new ByteStreamOutOstreamLE(stream);
  else
    out = new ByteStreamOutOstreamBE(stream);

  return open(out, header, compressor, requested_version, chunk_size);
}

BOOL LASwriterLAS::open(ByteStreamOut* stream, const LASheader* header, U32 compressor, I32 requested_version, I32 chunk_size)
{
  U32 i;

  if (stream == 0)
  {
    fprintf(stderr,"ERROR: ByteStreamOut pointer is zero\n");
    return FALSE;
  }
  this->stream = stream;

  if (header == 0)
  {
    fprintf(stderr,"ERROR: LASheader pointer is zero\n");
    return FALSE;
  }

  // check header contents

  if (!header->check()) return FALSE;

  // copy scale_and_offset
  quantizer.x_scale_factor = header->x_scale_factor;
  quantizer.y_scale_factor = header->y_scale_factor;
  quantizer.z_scale_factor = header->z_scale_factor;
  quantizer.x_offset = header->x_offset;
  quantizer.y_offset = header->y_offset;
  quantizer.z_offset = header->z_offset;

  // check if the requested point type is supported

  BOOL point_is_standard = TRUE;
  U8 point_data_format;
  U16 point_data_record_length;
  LASpoint point;
  if (header->laszip)
  {
    if (!point.init(&quantizer, header->laszip->num_items, header->laszip->items, header)) return FALSE;
    point_is_standard = header->laszip->is_standard(&point_data_format, &point_data_record_length);
  }
  else
  {
    if (!point.init(&quantizer, header->point_data_format, header->point_data_record_length, header)) return FALSE;
    point_data_format = header->point_data_format;
    point_data_record_length = header->point_data_record_length;
  }

  // do we need a laszip VLR (because we compress or use non-standard points?) 

  LASzip* laszip = 0;
  U32 laszip_vlr_data_size = 0;
  if (compressor || point_is_standard == FALSE)
  {
    laszip = new LASzip();
    laszip->setup(point.num_items, point.items, compressor);
    if (chunk_size > -1) laszip->set_chunk_size((U32)chunk_size);
    if (compressor == LASZIP_COMPRESSOR_NONE) laszip->request_version(0);
    else if (requested_version) laszip->request_version(requested_version);
    else laszip->request_version(2);
    laszip_vlr_data_size = 34 + 6*laszip->num_items;
  }

  // create and setup the point writer

  writer = new LASwritePoint();
  if (laszip)
  {
    if (!writer->setup(laszip->num_items, laszip->items, laszip)) return FALSE;
  }
  else
  {
    if (!writer->setup(point.num_items, point.items)) return FALSE;
  }

  // save the position where we start writing the header

  header_start_position = stream->tell();

  // write header variable after variable (to avoid alignment issues)

  if (!stream->putBytes((U8*)&(header->file_signature), 4))
  {
    fprintf(stderr,"ERROR: writing header->file_signature\n");
    return FALSE;
  }
  if (!stream->put16bitsLE((U8*)&(header->file_source_id)))
  {
    fprintf(stderr,"ERROR: writing header->file_source_id\n");
    return FALSE;
  }
  if (!stream->put16bitsLE((U8*)&(header->global_encoding)))
  {
    fprintf(stderr,"ERROR: writing header->global_encoding\n");
    return FALSE;
  }
  if (!stream->put32bitsLE((U8*)&(header->project_ID_GUID_data_1)))
  {
    fprintf(stderr,"ERROR: writing header->project_ID_GUID_data_1\n");
    return FALSE;
  }
  if (!stream->put16bitsLE((U8*)&(header->project_ID_GUID_data_2)))
  {
    fprintf(stderr,"ERROR: writing header->project_ID_GUID_data_2\n");
    return FALSE;
  }
  if (!stream->put16bitsLE((U8*)&(header->project_ID_GUID_data_3)))
  {
    fprintf(stderr,"ERROR: writing header->project_ID_GUID_data_3\n");
    return FALSE;
  }
  if (!stream->putBytes((U8*)&(header->project_ID_GUID_data_4), 8))
  {
    fprintf(stderr,"ERROR: writing header->project_ID_GUID_data_4\n");
    return FALSE;
  }
  // check version major
  if (header->version_major != 1)
  {
    fprintf(stderr,"WARNING: header->version_major is %d. writing 1 instead.\n", header->version_major);
    if (!stream->putByte(1))
    {
      fprintf(stderr,"ERROR: writing header->version_major\n");
      return FALSE;
    }
  }
  else
  {
    if (!stream->putByte(header->version_major))
    {
      fprintf(stderr,"ERROR: writing header->version_major\n");
      return FALSE;
    }
  }
  // check version major
  if (header->version_minor > 4)
  {
    fprintf(stderr,"WARNING: header->version_minor is %d. writing 4 instead.\n", header->version_minor);
    if (!stream->putByte(4))
    {
      fprintf(stderr,"ERROR: writing header->version_minor\n");
      return FALSE;
    }
  }
  else
  {
    if (!stream->putByte(header->version_minor))
    {
      fprintf(stderr,"ERROR: writing header->version_minor\n");
      return FALSE;
    }
  }
  if (!stream->putBytes((U8*)&(header->system_identifier), 32))
  {
    fprintf(stderr,"ERROR: writing header->system_identifier\n");
    return FALSE;
  }
  if (!stream->putBytes((U8*)&(header->generating_software), 32))
  {
    fprintf(stderr,"ERROR: writing header->generating_software\n");
    return FALSE;
  }
  if (!stream->put16bitsLE((U8*)&(header->file_creation_day)))
  {
    fprintf(stderr,"ERROR: writing header->file_creation_day\n");
    return FALSE;
  }
  if (!stream->put16bitsLE((U8*)&(header->file_creation_year)))
  {
    fprintf(stderr,"ERROR: writing header->file_creation_year\n");
    return FALSE;
  }
  if (!stream->put16bitsLE((U8*)&(header->header_size)))
  {
    fprintf(stderr,"ERROR: writing header->header_size\n");
    return FALSE;
  }
  U32 offset_to_point_data = header->offset_to_point_data;
  if (laszip) offset_to_point_data += (54 + laszip_vlr_data_size);
  if (header->vlr_lastiling) offset_to_point_data += (54 + 28);
  if (!stream->put32bitsLE((U8*)&offset_to_point_data))
  {
    fprintf(stderr,"ERROR: writing header->offset_to_point_data\n");
    return FALSE;
  }
  U32 number_of_variable_length_records = header->number_of_variable_length_records;
  if (laszip) number_of_variable_length_records++;
  if (header->vlr_lastiling) number_of_variable_length_records++;
  if (!stream->put32bitsLE((U8*)&(number_of_variable_length_records)))
  {
    fprintf(stderr,"ERROR: writing header->number_of_variable_length_records\n");
    return FALSE;
  }
  if (compressor) point_data_format |= 128;
  if (!stream->putByte(point_data_format))
  {
    fprintf(stderr,"ERROR: writing header->point_data_format\n");
    return FALSE;
  }
  if (!stream->put16bitsLE((U8*)&(header->point_data_record_length)))
  {
    fprintf(stderr,"ERROR: writing header->point_data_record_length\n");
    return FALSE;
  }
  if (!stream->put32bitsLE((U8*)&(header->number_of_point_records)))
  {
    fprintf(stderr,"ERROR: writing header->number_of_point_records\n");
    return FALSE;
  }
  for (i = 0; i < 5; i++)
  {
    if (!stream->put32bitsLE((U8*)&(header->number_of_points_by_return[i])))
    {
      fprintf(stderr,"ERROR: writing header->number_of_points_by_return[%d]\n", i);
      return FALSE;
    }
  }
  if (!stream->put64bitsLE((U8*)&(header->x_scale_factor)))
  {
    fprintf(stderr,"ERROR: writing header->x_scale_factor\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->y_scale_factor)))
  {
    fprintf(stderr,"ERROR: writing header->y_scale_factor\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->z_scale_factor)))
  {
    fprintf(stderr,"ERROR: writing header->z_scale_factor\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->x_offset)))
  {
    fprintf(stderr,"ERROR: writing header->x_offset\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->y_offset)))
  {
    fprintf(stderr,"ERROR: writing header->y_offset\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->z_offset)))
  {
    fprintf(stderr,"ERROR: writing header->z_offset\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->max_x)))
  {
    fprintf(stderr,"ERROR: writing header->max_x\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->min_x)))
  {
    fprintf(stderr,"ERROR: writing header->min_x\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->max_y)))
  {
    fprintf(stderr,"ERROR: writing header->max_y\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->min_y)))
  {
    fprintf(stderr,"ERROR: writing header->min_y\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->max_z)))
  {
    fprintf(stderr,"ERROR: writing header->max_z\n");
    return FALSE;
  }
  if (!stream->put64bitsLE((U8*)&(header->min_z)))
  {
    fprintf(stderr,"ERROR: writing header->min_z\n");
    return FALSE;
  }

  // special handling for LAS 1.3 or higher.
  if (header->version_minor >= 3)
  {
    // nobody currently includes waveform. we set the field always to zero
    if (header->start_of_waveform_data_packet_record != 0)
    {
#ifdef _WIN32
      fprintf(stderr,"WARNING: header->start_of_waveform_data_packet_record is %I64d. writing 0 instead.\n", header->start_of_waveform_data_packet_record);
#else
      fprintf(stderr,"WARNING: header->start_of_waveform_data_packet_record is %lld. writing 0 instead.\n", header->start_of_waveform_data_packet_record);
#endif
      U64 start_of_waveform_data_packet_record = 0;
      if (!stream->put64bitsLE((U8*)&start_of_waveform_data_packet_record))
      {
        fprintf(stderr,"ERROR: writing start_of_waveform_data_packet_record\n");
        return FALSE;
      }
    }
    else
    {
      if (!stream->put64bitsLE((U8*)&(header->start_of_waveform_data_packet_record)))
      {
        fprintf(stderr,"ERROR: writing header->start_of_waveform_data_packet_record\n");
        return FALSE;
      }
    }
  }

  // special handling for LAS 1.4 or higher.
  if (header->version_minor >= 4)
  {
    if (!stream->put64bitsLE((U8*)&(header->start_of_first_extended_variable_length_record)))
    {
      fprintf(stderr,"ERROR: writing header->start_of_first_extended_variable_length_record\n");
      return FALSE;
    }
    if (!stream->put32bitsLE((U8*)&(header->number_of_extended_variable_length_records)))
    {
      fprintf(stderr,"ERROR: writing header->number_of_extended_variable_length_records\n");
      return FALSE;
    }
    U64 value;
    if (header->number_of_point_records)
      value = header->number_of_point_records;
    else
      value = header->extended_number_of_point_records;
    if (!stream->put64bitsLE((U8*)&value))
    {
      fprintf(stderr,"ERROR: writing header->extended_number_of_point_records\n");
      return FALSE;
    }
    for (i = 0; i < 15; i++)
    {
      if ((i < 5) && header->number_of_points_by_return[i])
        value = header->number_of_points_by_return[i];
      else
        value = header->extended_number_of_points_by_return[i];
      if (!stream->put64bitsLE((U8*)&value))
      {
        fprintf(stderr,"ERROR: writing header->extended_number_of_points_by_return[%d]\n", i);
        return FALSE;
      }
    }
  }

  // write any number of user-defined bytes that might have been added into the header

  if (header->user_data_in_header_size)
  {
    if (header->user_data_in_header)
    {
      if (!stream->putBytes((U8*)header->user_data_in_header, header->user_data_in_header_size))
      {
        fprintf(stderr,"ERROR: writing %d bytes of data from header->user_data_in_header\n", header->user_data_in_header_size);
        return FALSE;
      }
    }
    else
    {
      fprintf(stderr,"ERROR: there should be %d bytes of data in header->user_data_in_header\n", header->user_data_in_header_size);
      return FALSE;
    }
  }

  // write variable length records variable after variable (to avoid alignment issues)

  for (i = 0; i < header->number_of_variable_length_records; i++)
  {
    // check variable length records contents

    if (header->vlrs[i].reserved != 0xAABB)
    {
//      fprintf(stderr,"WARNING: wrong header->vlrs[%d].reserved: %d != 0xAABB\n", i, header->vlrs[i].reserved);
    }

    // write variable length records variable after variable (to avoid alignment issues)

    if (!stream->put16bitsLE((U8*)&(header->vlrs[i].reserved)))
    {
      fprintf(stderr,"ERROR: writing header->vlrs[%d].reserved\n", i);
      return FALSE;
    }
    if (!stream->putBytes((U8*)header->vlrs[i].user_id, 16))
    {
      fprintf(stderr,"ERROR: writing header->vlrs[%d].user_id\n", i);
      return FALSE;
    }
    if (!stream->put16bitsLE((U8*)&(header->vlrs[i].record_id)))
    {
      fprintf(stderr,"ERROR: writing header->vlrs[%d].record_id\n", i);
      return FALSE;
    }
    if (!stream->put16bitsLE((U8*)&(header->vlrs[i].record_length_after_header)))
    {
      fprintf(stderr,"ERROR: writing header->vlrs[%d].record_length_after_header\n", i);
      return FALSE;
    }
    if (!stream->putBytes((U8*)header->vlrs[i].description, 32))
    {
      fprintf(stderr,"ERROR: writing header->vlrs[%d].description\n", i);
      return FALSE;
    }

    // write the data following the header of the variable length record

    if (header->vlrs[i].record_length_after_header)
    {
      if (header->vlrs[i].data)
      {
        if (!stream->putBytes((U8*)header->vlrs[i].data, header->vlrs[i].record_length_after_header))
        {
          fprintf(stderr,"ERROR: writing %d bytes of data from header->vlrs[%d].data\n", header->vlrs[i].record_length_after_header, i);
          return FALSE;
        }
      }
      else
      {
        fprintf(stderr,"ERROR: there should be %d bytes of data in header->vlrs[%d].data\n", header->vlrs[i].record_length_after_header, i);
        return FALSE;
      }
    }
  }

  // write laszip VLR with compression parameters

  if (laszip)
  {
    // write variable length records variable after variable (to avoid alignment issues)

    U16 reserved = 0xAABB;
    if (!stream->put16bitsLE((U8*)&(reserved)))
    {
      fprintf(stderr,"ERROR: writing reserved %d\n", (I32)reserved);
      return FALSE;
    }
    U8 user_id[16] = "laszip encoded\0";
    if (!stream->putBytes((U8*)user_id, 16))
    {
      fprintf(stderr,"ERROR: writing user_id %s\n", user_id);
      return FALSE;
    }
    U16 record_id = 22204;
    if (!stream->put16bitsLE((U8*)&(record_id)))
    {
      fprintf(stderr,"ERROR: writing record_id %d\n", (I32)record_id);
      return FALSE;
    }
    U16 record_length_after_header = laszip_vlr_data_size;
    if (!stream->put16bitsLE((U8*)&(record_length_after_header)))
    {
      fprintf(stderr,"ERROR: writing record_length_after_header %d\n", (I32)record_length_after_header);
      return FALSE;
    }
    char description[32];
    memset(description, 0, 32);
    sprintf(description, "by laszip of LAStools (%d)", LAS_TOOLS_VERSION);  
    if (!stream->putBytes((U8*)description, 32))
    {
      fprintf(stderr,"ERROR: writing description %s\n", description);
      return FALSE;
    }
    // write the data following the header of the variable length record
    //     U16  compressor         2 bytes 
    //     U16  coder              2 bytes 
    //     U8   version_major      1 byte 
    //     U8   version_minor      1 byte
    //     U16  version_revision   2 bytes
    //     U32  options            4 bytes 
    //     U32  chunk_size         4 bytes
    //     I64  num_points         8 bytes
    //     I64  num_bytes          8 bytes
    //     U16  num_items          2 bytes
    //        U16 type                2 bytes * num_items
    //        U16 size                2 bytes * num_items
    //        U16 version             2 bytes * num_items
    // which totals 34+6*num_items

    if (!stream->put16bitsLE((U8*)&(laszip->compressor)))
    {
      fprintf(stderr,"ERROR: writing compressor %d\n", (I32)compressor);
      return FALSE;
    }
    if (!stream->put16bitsLE((U8*)&(laszip->coder)))
    {
      fprintf(stderr,"ERROR: writing coder %d\n", (I32)laszip->coder);
      return FALSE;
    }
    if (!stream->putByte(laszip->version_major))
    {
      fprintf(stderr,"ERROR: writing version_major %d\n", laszip->version_major);
      return FALSE;
    }
    if (!stream->putByte(laszip->version_minor))
    {
      fprintf(stderr,"ERROR: writing version_minor %d\n", laszip->version_minor);
      return FALSE;
    }
    if (!stream->put16bitsLE((U8*)&(laszip->version_revision)))
    {
      fprintf(stderr,"ERROR: writing version_revision %d\n", laszip->version_revision);
      return FALSE;
    }
    if (!stream->put32bitsLE((U8*)&(laszip->options)))
    {
      fprintf(stderr,"ERROR: writing options %d\n", (I32)laszip->options);
      return FALSE;
    }
    if (!stream->put32bitsLE((U8*)&(laszip->chunk_size)))
    {
      fprintf(stderr,"ERROR: writing chunk_size %d\n", laszip->chunk_size);
      return FALSE;
    }
    if (!stream->put64bitsLE((U8*)&(laszip->num_points)))
    {
      fprintf(stderr,"ERROR: writing num_points %d\n", (I32)laszip->num_points);
      return FALSE;
    }
    if (!stream->put64bitsLE((U8*)&(laszip->num_bytes)))
    {
      fprintf(stderr,"ERROR: writing num_bytes %d\n", (I32)laszip->num_bytes);
      return FALSE;
    }
    if (!stream->put16bitsLE((U8*)&(laszip->num_items)))
    {
      fprintf(stderr,"ERROR: writing num_items %d\n", laszip->num_items);
      return FALSE;
    }
    for (i = 0; i < laszip->num_items; i++)
    {
      if (!stream->put16bitsLE((U8*)&(laszip->items[i].type)))
      {
        fprintf(stderr,"ERROR: writing type %d of item %d\n", laszip->items[i].type, i);
        return FALSE;
      }
      if (!stream->put16bitsLE((U8*)&(laszip->items[i].size)))
      {
        fprintf(stderr,"ERROR: writing size %d of item %d\n", laszip->items[i].size, i);
        return FALSE;
      }
      if (!stream->put16bitsLE((U8*)&(laszip->items[i].version)))
      {
        fprintf(stderr,"ERROR: writing version %d of item %d\n", laszip->items[i].version, i);
        return FALSE;
      }
    }
  }

  // write laszip VLR with compression parameters

  if (header->vlr_lastiling)
  {
    // write variable length records variable after variable (to avoid alignment issues)

    U16 reserved = 0xAABB;
    if (!stream->put16bitsLE((U8*)&(reserved)))
    {
      fprintf(stderr,"ERROR: writing reserved %d\n", (I32)reserved);
      return FALSE;
    }
    U8 user_id[16] = "lastools tile\0\0";
    if (!stream->putBytes((U8*)user_id, 16))
    {
      fprintf(stderr,"ERROR: writing user_id %s\n", user_id);
      return FALSE;
    }
    U16 record_id = 22204;
    if (!stream->put16bitsLE((U8*)&(record_id)))
    {
      fprintf(stderr,"ERROR: writing record_id %d\n", (I32)record_id);
      return FALSE;
    }
    U16 record_length_after_header = 28;
    if (!stream->put16bitsLE((U8*)&(record_length_after_header)))
    {
      fprintf(stderr,"ERROR: writing record_length_after_header %d\n", (I32)record_length_after_header);
      return FALSE;
    }
    U8 description[32] = "LAStools tile for streaming TIN";
    if (!stream->putBytes((U8*)description, 32))
    {
      fprintf(stderr,"ERROR: writing description %s\n", description);
      return FALSE;
    }

    // write the data following the header of the variable length record
    //     U32  level           4 bytes 
    //     U32  level_index     4 bytes 
    //     U32  implicit_levels + overlap bit + reversible bit 4 bytes 
    //     F32  min_x           4 bytes 
    //     F32  max_x           4 bytes 
    //     F32  min_y           4 bytes 
    //     F32  max_y           4 bytes 
    // which totals 28 bytes

    if (!stream->put32bitsLE((U8*)&(header->vlr_lastiling->level)))
    {
      fprintf(stderr,"ERROR: writing header->vlr_lastiling->level %u\n", header->vlr_lastiling->level);
      return FALSE;
    }
    if (!stream->put32bitsLE((U8*)&(header->vlr_lastiling->level_index)))
    {
      fprintf(stderr,"ERROR: writing header->vlr_lastiling->level_index %u\n", header->vlr_lastiling->level_index);
      return FALSE;
    }
    if (!stream->put32bitsLE(((U8*)header->vlr_lastiling)+8))
    {
      fprintf(stderr,"ERROR: writing header->vlr_lastiling->implicit_levels %u\n", header->vlr_lastiling->implicit_levels);
      return FALSE;
    }
    if (!stream->put32bitsLE((U8*)&(header->vlr_lastiling->min_x)))
    {
      fprintf(stderr,"ERROR: writing header->vlr_lastiling->min_x %g\n", header->vlr_lastiling->min_x);
      return FALSE;
    }
    if (!stream->put32bitsLE((U8*)&(header->vlr_lastiling->max_x)))
    {
      fprintf(stderr,"ERROR: writing header->vlr_lastiling->max_x %g\n", header->vlr_lastiling->max_x);
      return FALSE;
    }
    if (!stream->put32bitsLE((U8*)&(header->vlr_lastiling->min_y)))
    {
      fprintf(stderr,"ERROR: writing header->vlr_lastiling->min_y %g\n", header->vlr_lastiling->min_y);
      return FALSE;
    }
    if (!stream->put32bitsLE((U8*)&(header->vlr_lastiling->max_y)))
    {
      fprintf(stderr,"ERROR: writing header->vlr_lastiling->max_y %g\n", header->vlr_lastiling->max_y);
      return FALSE;
    }
  }

  // write any number of user-defined bytes that might have been added after the header

  if (header->user_data_after_header_size)
  {
    if (header->user_data_after_header)
    {
      if (!stream->putBytes((U8*)header->user_data_after_header, header->user_data_after_header_size))
      {
        fprintf(stderr,"ERROR: writing %d bytes of data from header->user_data_after_header\n", header->user_data_after_header_size);
        return FALSE;
      }
    }
    else
    {
      fprintf(stderr,"ERROR: there should be %d bytes of data in header->user_data_after_header\n", header->user_data_after_header_size);
      return FALSE;
    }
  }

  // initialize the point writer

  if (!writer->init(stream)) return FALSE;

  npoints = header->number_of_point_records;
  p_count = 0;

  return TRUE;
}

BOOL LASwriterLAS::write_point(const LASpoint* point)
{
  p_count++;
  return writer->write(point->point);
}

BOOL LASwriterLAS::chunk()
{
  return writer->chunk();
}

BOOL LASwriterLAS::update_header(const LASheader* header, BOOL use_inventory, BOOL update_extra_bytes)
{
  I32 i;
  if (header == 0)
  {
    fprintf(stderr,"ERROR: header pointer is zero\n");
    return FALSE;
  }
  if (stream == 0)
  {
    fprintf(stderr,"ERROR: stream pointer is zero\n");
    return FALSE;
  }
  if (!stream->isSeekable())
  {
    fprintf(stderr,"ERROR: stream is not seekable\n");
    return FALSE;
  }
  if (use_inventory && inventory.active())
  {
    stream->seek(header_start_position+107);
    if (!stream->put32bitsLE((U8*)&(inventory.number_of_point_records)))
    {
      fprintf(stderr,"ERROR: updating header->number_of_point_records\n");
      return FALSE;
    }
    npoints = inventory.number_of_point_records;
    for (i = 0; i < 5; i++)
    {
      if (!stream->put32bitsLE((U8*)&(inventory.number_of_points_by_return[i+1])))
      {
        fprintf(stderr,"ERROR: updating header->number_of_points_by_return[%d]\n", i);
        return FALSE;
      }
    }
    stream->seek(header_start_position+179);
    F64 value;
    value = header->get_x(inventory.raw_max_x);
    if (!stream->put64bitsLE((U8*)&value))
    {
      fprintf(stderr,"ERROR: updating header->max_x\n");
      return FALSE;
    }
    value = header->get_x(inventory.raw_min_x);
    if (!stream->put64bitsLE((U8*)&value))
    {
      fprintf(stderr,"ERROR: updating header->min_x\n");
      return FALSE;
    }
    value = header->get_y(inventory.raw_max_y);
    if (!stream->put64bitsLE((U8*)&value))
    {
      fprintf(stderr,"ERROR: updating header->max_y\n");
      return FALSE;
    }
    value = header->get_y(inventory.raw_min_y);
    if (!stream->put64bitsLE((U8*)&value))
    {
      fprintf(stderr,"ERROR: updating header->min_y\n");
      return FALSE;
    }
    value = header->get_z(inventory.raw_max_z);
    if (!stream->put64bitsLE((U8*)&value))
    {
      fprintf(stderr,"ERROR: updating header->max_z\n");
      return FALSE;
    }
    value = header->get_z(inventory.raw_min_z);
    if (!stream->put64bitsLE((U8*)&value))
    {
      fprintf(stderr,"ERROR: updating header->min_z\n");
      return FALSE;
    }
  }
  else
  {
    stream->seek(header_start_position+107);
    if (!stream->put32bitsLE((U8*)&(header->number_of_point_records)))
    {
      fprintf(stderr,"ERROR: updating header->number_of_point_records\n");
      return FALSE;
    }
    npoints = header->number_of_point_records;
    for (i = 0; i < 5; i++)
    {
      if (!stream->put32bitsLE((U8*)&(header->number_of_points_by_return[i])))
      {
        fprintf(stderr,"ERROR: updating header->number_of_points_by_return[%d]\n", i);
        return FALSE;
      }
    }
    stream->seek(header_start_position+179);
    if (!stream->put64bitsLE((U8*)&(header->max_x)))
    {
      fprintf(stderr,"ERROR: updating header->max_x\n");
      return FALSE;
    }
    if (!stream->put64bitsLE((U8*)&(header->min_x)))
    {
      fprintf(stderr,"ERROR: updating header->min_x\n");
      return FALSE;
    }
    if (!stream->put64bitsLE((U8*)&(header->max_y)))
    {
      fprintf(stderr,"ERROR: updating header->max_y\n");
      return FALSE;
    }
    if (!stream->put64bitsLE((U8*)&(header->min_y)))
    {
      fprintf(stderr,"ERROR: updating header->min_y\n");
      return FALSE;
    }
    if (!stream->put64bitsLE((U8*)&(header->max_z)))
    {
      fprintf(stderr,"ERROR: updating header->max_z\n");
      return FALSE;
    }
    if (!stream->put64bitsLE((U8*)&(header->min_z)))
    {
      fprintf(stderr,"ERROR: updating header->min_z\n");
      return FALSE;
    }
  }
  if (update_extra_bytes)
  {
    if (header->number_extra_attributes)
    {
      I64 start = header_start_position + header->header_size;
      for (i = 0; i < (I32)header->number_of_variable_length_records; i++)
      {
        start += 54;
        if (header->vlrs[i].data == (char*)header->extra_attributes)
        {
          break;
        }
        else
        {
          start += header->vlrs[i].record_length_after_header;
        }
      }
      stream->seek(start);
      if (!stream->putBytes((U8*)header->vlrs[i].data, header->vlrs[i].record_length_after_header))
      {
        fprintf(stderr,"ERROR: writing %d bytes of data from header->vlrs[%d].data\n", header->vlrs[i].record_length_after_header, i);
        return FALSE;
      }
    }
  }
  stream->seekEnd();
  return TRUE;
}

I64 LASwriterLAS::close(BOOL update_header)
{
  I64 bytes = 0;

  if (p_count != npoints)
  {
#ifdef _WIN32
    fprintf(stderr,"WARNING: written %I64d points but expected %I64d points\n", p_count, npoints);
#else
    fprintf(stderr,"WARNING: written %lld points but expected %lld points\n", p_count, npoints);
#endif
  }

  if (writer) 
  {
    writer->done();
    delete writer;
    writer = 0;
  }

  if (stream)
  {
    if (update_header && p_count != npoints)
    {
      if (!stream->isSeekable())
      {
#ifdef _WIN32
        fprintf(stderr, "ERROR: stream not seekable. cannot update header from %I64d to %I64d points.\n", npoints, p_count);
#else
        fprintf(stderr, "ERROR: stream not seekable. cannot update header from %lld to %lld points.\n", npoints, p_count);
#endif
      }
      else
      {
	      stream->seek(header_start_position+107);
	      stream->put32bitsLE((U8*)&p_count);
        stream->seekEnd();
      }
    }
    bytes = stream->tell() - header_start_position;
    delete stream;
    stream = 0;
  }

  if (file)
  {
    fclose(file);
    file = 0;
  }

  npoints = p_count;
  p_count = 0;

  return bytes;
}

LASwriterLAS::LASwriterLAS()
{
  file = 0;
  stream = 0;
  writer = 0;
}

LASwriterLAS::~LASwriterLAS()
{
  if (writer || stream) close();
}
